A network storage system includes multiple storage devices or disks coupled with a storage server that has interconnecting hardware and control hardware. The storage server stores and retrieves data on behalf of clients of the storage system. The services provided by the storage server involve the exchange of data between clients and the disks. The exchange of data between the clients and the disks often involves buffering data. The buffers may be cached to improve the speed and performance of the storage server. The storage server includes a buffer cache in memory to store the buffers. The buffers are generally blocks of data. The exchange or other access to data within the storage server typically includes access to data of multiple different types.
There have been basically two, traditional approaches to providing a buffer cache within the storage server. One approach is to provide memory that is hard separated (through either physical separation or partitioning) into multiple, separate physical caches. Hard separated caches are unable to share data. Each separate cache can have different rules that apply to data in the cache. With hard-separated caches, different types of data can be handled differently based upon the cache handling the data. However, hard separation of memory into physical caches traditionally requires expensive configuration. Additionally, hard division of memory into separate caches is inflexible as to cache size. Thus, the cost of physical division is typically higher than a uniform cache because enough memory must be allocated to ensure maximum needs for each different type of data is met. Additionally, the reconfiguration of the system can be burdensome if a different data type is introduced or a different set of rules is desired to be applied to a data type.
Another approach to caching buffers is the use of a large uniform buffer cache. A large uniform buffer cache is frequently managed according to a least recently used (LRU) buffer replacement scheme, although other approaches are known (e.g., random, last-in first-out (LIFO), most recently used (MRU), least-frequently-used (LFU), etc.). LRU approaches are traditionally rather indiscriminate as to the relative importance of a particular data type within a system. In general, traditional uniform cache approaches fail to provide adequate controls for different data types of the buffers. Not only does the treatment of different data types traditionally encounter problems with a lack of granular control of buffer replacement, but traditional LRU caches fail to adequately address different user-level objects with different buffer cache policies. The results of the traditional problems of a single LRU cache can be poor cache performance where data that is wanted is removed from a cache while unwanted data may be kept.